package ClientDB;

import org.eclipse.swt.SWT;
import DataConnectionInterface.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.*;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class Board extends Canvas {
   
    //GUI objects
    private Shell shell;
    private Display display;
    private Runnable runnable;
    private Button card1Button;
    private Button card2Button;
    private Button card3Button;
    private Button card4Button;
    private Button nextTurn;
    private Button undoButton;
    private Button quitButton;
    private CardDeck deck;
    private ICard card1;
    private ICard card2;
    private ICard card3;
    private ICard card4;
    private ICard card5;
    private ICard card6;    
    private Image cardTemplate;
    private Image attackIcon;
    private Image shieldIcon;
    private Image deckBackground;
    private Text playerCard;
    private Text rivalsCard;
    private Text universeText;
    private Text whosTurnIsIt;
    private MessageBox rivalMoveMsg;
    private MessageBox gameOverMsg;
    
    // GUI constants:
    private final int DELAY = 140;
    private final int WIDTH = 500;
    private final int HEIGHT = 500;
    private final Color WHITE = new Color(this.getDisplay(), 255, 255, 255);
    private final int card1Xpos = 50;
    private final int playercardsXOffset = 200;
    private final int card5Xpos = 250;
    private final int playingcardsXOffset = 250;
    private final int card1ButtonXpos = 60;
    private final int playerCardsYPos = 310;
    private final int playingCardsYPos = 60;
    private final int selectCardButtonY = 500;
    private final int selectCardButtonWidth = 100;
    private final int selectCardButtonHeight = 30;
    
    // Game Logic
    private boolean cardChosenRedraw = false;
    private boolean cardChosen = false;
    private int chosenCardNum;
    private boolean playersTurn;   
    private boolean cardSent = false;
    private boolean playerWon;
    private boolean playerWonMatch = false;
    private boolean roundComplete = false;
    private boolean gotRivalsResponse = false;
    private Card[] cardSet = new Card[4];
    private ArrayList<Card> turnList = new ArrayList<Card>();
    private int numOfCardsInGame = 0;
    private String universe;
    
    // Networking objects
    private ObjectInputStream in = null;
    private ObjectOutputStream out = null;
    private Socket client = null;    
    private Thread gameThread;
    private boolean nextButtinPushed = false;
    private boolean finishedInit = true;

    
    public Board(final Shell shell, ArrayList<Card> cardsList, boolean playerBegins, 
    					final ObjectInputStream in, final ObjectOutputStream out, final Socket client, String universe) {
        super(shell, SWT.NULL);
        this.universe = universe;
        this.shell = shell;
        display = shell.getDisplay();
        setSize(WIDTH, HEIGHT);
        this.addPaintListener(new BoardPaintListener());
        this.deck = new CardDeck(shell.getDisplay(), cardsList); // should be this.deck = gamedeck
        initImages();
        this.setBackgroundImage(deckBackground);
        initPlayerCards();
        createButtons();
        addButtonListeners();
        initText();

        this.in = in;
        this.out = out;
        this.client = client;
        playersTurn = playerBegins;
        initRound(playersTurn);
        initGame(shell.getDisplay());
                
        gameThread = new Thread(new Runnable(){
			@Override
			public void run() {
				runGame();
			}
        });
        gameThread.start();
    }
    
    public void runGame(){
		
		boolean init = true;
		int numOfRivalsCards;
		Card rivalCard = null, myCard = null;
		
		try{
			while(true){
				Thread.sleep(100);
				numOfRivalsCards = (Integer) in.readObject();
				
				// check if game is over
				if(numOfCardsInGame == 0 || numOfRivalsCards == 0){
					setWinner(numOfRivalsCards);
					return;
				}
				
				// game is not over...
				out.writeObject(new String("NONE"));
				
				if(!init) {
					// update playing cards
					myCard.decreaseHitPoints(rivalCard.getAttack());
					nextButtinPushed = false;
					roundComplete = true;
				}
				init = false;
				
				if(playersTurn){
					while(!nextButtinPushed)
						Thread.sleep(100);
						
					
					cardChosenRedraw = true;
					
					myCard = cardSet[chosenCardNum - 1];
					// send chosen card to server
					out.writeObject(new Card(myCard.getAttack(), myCard.getDefense(),myCard.getName()));
					cardSent = true;
					
					// get rival's card
					rivalCard = (Card) in.readObject();
					
					card6 = deck.getRivalCard();
					card6.setCardInfo(rivalCard);
					cardChosenRedraw = true;
					
					playerWon = false;
					playersTurn = false;
					finishedInit = false;
					
				} else {
					
					while(!finishedInit){
						Thread.sleep(50);
					}
					finishedInit = false;
					
					// get rival's card
					rivalCard = (Card) in.readObject();
					gotRivalsResponse = true;
					card6 = deck.getRivalCard();
					card6.setCardInfo(rivalCard);
					cardChosenRedraw = true;
					
					while(!nextButtinPushed)
						Thread.sleep(100);
					
					nextButtinPushed = false;
					cardChosenRedraw = true;
					
					myCard = cardSet[chosenCardNum - 1];
					// send chosen card
					out.writeObject(new Card(myCard.getAttack(),myCard.getDefense(),myCard.getName())); 
					
					playerWon = true;
					playersTurn = true;
					
				}
				
				cardSent = false;
				
				if(myCard.getDefense() - rivalCard.getAttack() <= 0) {
					if(deck.getCardsLeft() == 0) numOfCardsInGame--;
					turnList.remove(myCard);
				}
				
				setWaitingCards(myCard);
				
				out.writeObject(new Integer(numOfCardsInGame));
				gotRivalsResponse = false;
			}
		} catch(Exception e){
			displayError();
		}
    }
    
    /**	Displays message box with an error and exits
     * 
     */
    public void displayError(){
    	display.asyncExec(new Runnable(){
			@Override
			public void run() {
		    	MessageBox errorMsg = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
		    	errorMsg.setMessage("Exit");
		    	errorMsg.setText("Error Occured");
	 	    	errorMsg.open();
		    	quitButtonLogic();
	 		}
    	});
    }

    /** Sets the winner
     * 
     * @param numOfRivalsCards int
     */
    private void setWinner(int numOfRivalsCards){
    	
    	if(numOfCardsInGame == 0 && numOfRivalsCards != 0) {
			playerWonMatch = false;
		}
		else if(numOfRivalsCards == 0 && numOfCardsInGame != 0) {
			playerWonMatch = true;
		}
		else if(numOfRivalsCards == 0 && numOfCardsInGame == 0){
			if(playersTurn) {
				playerWonMatch = true;
			}
			else {
				playerWonMatch = false;
			}
		}
    	
    	try {
    		out.writeObject(new String(playerWonMatch ? "WIN" : "LOSE"));
		} catch (IOException e) {
		}
    	
    	gameOverMB();
    	
    }

    // sets for each cards its its waiting turns
    private void setWaitingCards(Card card){
    	for(Card c: turnList)
    		if(c.getWaitingTurns() != 0) c.decreaseWaitingTurns();
    	
    	if(!turnList.contains(card)) return;
    	Collections.sort(turnList);
    	
    	int i;
    	for(i = 0;i < turnList.size();i++)
    		if(turnList.get(i).equals(card)) break;
    	
    	turnList.get(i).setWaitingTurns(i);
    	
    }
    
    /** returns if a move is valid or not
     * 
     */
    private boolean isValidMove(){
    	Card card = cardSet[chosenCardNum - 1];
    	if(card.getWaitingTurns() > 0){
    		return false;
    	}
    	
    	return true;
    }
    
    /** Displays message box if move is invalid
     * 
     */
    private void invalidMoveMB() {
    	display.asyncExec(new Runnable(){
			@Override
			public void run() {
		    	MessageBox invalidMoveMsg = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
		    	invalidMoveMsg.setMessage("Can't use this card at current round");
		    	invalidMoveMsg.setText("Invalid move");
		    	invalidMoveMsg.open();				
			}
    	});
    	
    }
    
    /** Displays message box for game end and returns to main menu
     * 
     */
    private void gameOverMB() {
    	display.asyncExec(new Runnable(){
			@Override
			public void run() {
		    	gameOverMsg = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
		    	gameOverMsg.setMessage((playerWonMatch) ? "You Win!" : "You Lose!");
		    	gameOverMsg.setText("Game Over");
		    	gameOverMsg.open();				
				shell.close();
				
				// now we return to main menu...
				Display display = Display.getDefault();
		    	Shell newShell = new Shell(display, SWT.MENU);
		    	new Menu(newShell);
		    	
		    	newShell.open();
		        while (!newShell.isDisposed()) {
		        	if (!display.readAndDispatch()) {
		        		display.sleep();
		        	}
		        }
		    	
			}
    	});
    	
    }
    
    /** Quits the game orderly, closing relevant networking
     *  
     */
    private void quitButtonLogic() {
    	shell.close();
    	
    	try {
    		if(!client.isOutputShutdown()) out.close();
    		if(!client.isInputShutdown()) in.close();
			if(!client.isClosed())client.close();
		} catch (IOException e) {
			
		}
		
		System.exit(0);
    	
    }
    
    /** Initializes all the skeleton images used
 	* 
 	*/
    private void initImages() {
        this.cardTemplate = new Image(shell.getDisplay(), "template-card.jpg");
        this.attackIcon = new Image(shell.getDisplay(), "sword-icon.png");
        this.shieldIcon = new Image(shell.getDisplay(), "shield-icon.jpg");
        this.deckBackground = new Image(shell.getDisplay(), "game-view.jpg");
    }

    /** Initializes all the text fields used
     * 
     */    
    private void initText() {
        this.playerCard = new Text(this, SWT.NORMAL);
        playerCard.setFont(new Font(shell.getDisplay(), "Monotype Corsiva", 14, SWT.ITALIC));
        playerCard.setText("Your card");
        playerCard.setBounds(270, 251, 77, 18);
        playerCard.setEnabled(false);
        playerCard.setBackground(WHITE);

        this.rivalsCard = new Text(this, SWT.NORMAL);
        rivalsCard.setFont(new Font(shell.getDisplay(), "Monotype Corsiva", 14, SWT.ITALIC));
        rivalsCard.setText("Rivals card");
        rivalsCard.setBounds(520, 251, 90, 18);
        rivalsCard.setEnabled(false);
        rivalsCard.setBackground(WHITE);

        this.universeText = new Text(this, SWT.READ_ONLY);
        universeText.setFont(new Font(shell.getDisplay(), "Monotype Corsiva", 18, SWT.BOLD));
        universeText.setText(universe);
        universeText.setBackgroundImage(deckBackground);
        universeText.setBounds(1, 1, 350, 35);

        this.whosTurnIsIt = new Text(this, SWT.READ_ONLY);
        whosTurnIsIt.setFont(new Font(shell.getDisplay(), "Monotype Corsiva", 14, SWT.ITALIC));
        whosTurnIsIt.setBackground(getBackground());
        whosTurnIsIt.setBounds(400, 1, 91, 23);
    }

    // initializes cards
    private void initPlayerCards() {
    	for(int i = 0;i < 4;i++)
    		cardSet[i] = null;
    	
        card1 = deck.getNextCard();
        if(card1 != deck.getEmptyCard())
        	cardSet[0] = new Card(card1.getAttack(), card1.getDefense(), card1.getName());
        
        card2 = deck.getNextCard();
        if(card2 != deck.getEmptyCard())
        	cardSet[1] = new Card(card2.getAttack(), card2.getDefense(), card2.getName());
        
        card3 = deck.getNextCard();
        if(card3 != deck.getEmptyCard())
        	cardSet[2] = new Card(card3.getAttack(), card3.getDefense(), card3.getName());
        
        card4 = deck.getNextCard();
        if(card4 != deck.getEmptyCard())
        	cardSet[3] = new Card(card4.getAttack(), card4.getDefense(), card4.getName());
        
        card5 = deck.getEmptyCard();
        card6 = deck.getEmptyCard();
        
        for(int i = 0;i < 4;i++){
        	if(cardSet[i] != null) {
        		numOfCardsInGame++;
	        	cardSet[i].setWaitingTurns(0);
	        	turnList.add(cardSet[i]);
        	}
        }
        
    }
    
    /** Initializes all the Buttons:  
     * 
     */    
    private void createButtons() {
        this.card1Button = new Button(this, SWT.ARROW);
        card1Button.setBounds(card1ButtonXpos, selectCardButtonY, selectCardButtonWidth, selectCardButtonHeight);
        this.card2Button = new Button(this, SWT.ARROW);
        card2Button.setBounds(card1ButtonXpos + playercardsXOffset, selectCardButtonY, selectCardButtonWidth, selectCardButtonHeight);
        this.card3Button = new Button(this, SWT.ARROW);
        card3Button.setBounds(card1ButtonXpos + (2*playercardsXOffset), selectCardButtonY, selectCardButtonWidth, selectCardButtonHeight);
        this.card4Button = new Button(this, SWT.ARROW);
        card4Button.setBounds(card1ButtonXpos + (3*playercardsXOffset), selectCardButtonY, selectCardButtonWidth, selectCardButtonHeight);
        this.nextTurn = new Button(this, SWT.FLAT);
        nextTurn.setBounds(390, 80, 100, 30);
        nextTurn.setText("NEXT TURN");
        this.quitButton = new Button(this, SWT.FLAT);
        quitButton.setBounds(760, 50, 100, 30);
        quitButton.setText("QUIT");
        this.undoButton = new Button(this, SWT.FLAT);
        undoButton.setBounds(390, 180, 100, 30);
        undoButton.setText("UNDO");
    }

    /** Adds selection listeners to the buttons
     * 
     */
    private void addButtonListeners() {
        card1Button.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent e) {
            	if(cardSent) return;
            	if(cardSet[0] == null) return;
            	if (card1 != deck.getEmptyCard()) {
                    if (cardChosen) {
                        undoChosenCard();
                    }
                    card5 = card1;
                    card1 = deck.getEmptyCard();
                    chosenCardNum = 1;
                    cardChosen = cardChosenRedraw = true;
                   
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                
            }
        });
        card2Button.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent e) {
            	if(cardSent) return;
            	if(cardSet[1] == null) return;
            	if (card2 != deck.getEmptyCard()) {
                    if (cardChosen) {
                        undoChosenCard();
                    }
                    card5 = card2;
                    card2 = deck.getEmptyCard();
                    chosenCardNum = 2;
                    cardChosen = cardChosenRedraw = true;
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                
            }
        });
        card3Button.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent e) {
            	if(cardSent) return;
            	if(cardSet[2] == null) return;
            	if (card3 != deck.getEmptyCard()) {
                    if (cardChosen) {
                        undoChosenCard();
                    }
                    card5 = card3;
                    card3 = deck.getEmptyCard();
                    chosenCardNum = 3;
                    cardChosen = cardChosenRedraw = true;
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                
            }
        });
        card4Button.addSelectionListener(new SelectionListener() {
        	
            public void widgetSelected(SelectionEvent e) {
            	if(cardSent) return;
            	if(cardSet[3] == null) return;
                if (card4 != deck.getEmptyCard()) {
                    if (cardChosen) {
                        undoChosenCard();
                    }
                    card5 = card4;
                    card4 = deck.getEmptyCard();
                    chosenCardNum = 4;
                    cardChosen = cardChosenRedraw = true;
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                
            }
        });
        undoButton.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent e) {
            	if(cardSent) return;
                if (cardChosen) {
                    undoChosenCard();
                    cardChosen = false;
                    cardChosenRedraw = true;
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                
            }
        });
        nextTurn.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent e) {
            	if(nextButtinPushed) return;
            	if(!cardChosen) return;
            	if(!gotRivalsResponse && !playersTurn) 
            		return;
            	boolean isValid = isValidMove();
            	if(!isValid) {
            		invalidMoveMB();
            			return;
            	}
                if (cardChosen) {
                	nextButtinPushed = true;
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                
            }
        });
        quitButton.addSelectionListener(new SelectionListener() {

            public void widgetSelected(SelectionEvent e) {
                quitButtonLogic();
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                
            }
        });
    }

    /** Adds tool-tips to all the buttons
     * 
     */
    private void addButtonToolTips() {
        if (card1 == deck.getEmptyCard()) {
            card1Button.setToolTipText(null);
        } else {
            card1Button.setToolTipText("Select " + card1.getName());
        }
        if (card2 == deck.getEmptyCard()) {
            card2Button.setToolTipText(null);
        } else {
            card2Button.setToolTipText("Select " + card2.getName());
        }
        if (card3 == deck.getEmptyCard()) {
            card3Button.setToolTipText(null);
        } else {
            card3Button.setToolTipText("Select " + card3.getName());
        }
        if (card4 == deck.getEmptyCard()) {
            card4Button.setToolTipText(null);
        } else {
            card4Button.setToolTipText("Select " + card4.getName());
        }
        if (cardChosen) {
            nextTurn.setToolTipText("Proceed to card battle");
        } else {
            nextTurn.setToolTipText("First choose a card");
        }
        if (cardChosen) {
            undoButton.setToolTipText("Undo move");
        } else {
            undoButton.setToolTipText("No card selected yet");
        }
    }

    /** Round initialization. Sets who starts playing
     * 
     */
    private boolean initRound(boolean playerWon) {        
        if (playersTurn) {
            whosTurnIsIt.setText("You start");
        } else {
            whosTurnIsIt.setText("Rival starts");
        }
        cardChosen = false;
        return true;
    }

    /** Puts an emptyCard Card type as card5
     *  Return the previous card to its original position
     */
    private void undoChosenCard() {
        switch (chosenCardNum) {
            case (1):
                card1 = card5;
                card5 = deck.getEmptyCard();
                break;
            case (2):
                card2 = card5;
                card5 = deck.getEmptyCard();
                break;
            case (3):
                card3 = card5;
                card5 = deck.getEmptyCard();
                break;
            case (4):
                card4 = card5;
                card5 = deck.getEmptyCard();
                break;
        }
    }

    /** Displays a message box when a turn is completed
     * 
     */
    private void displayMB() {
        rivalMoveMsg = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
        rivalMoveMsg.setMessage("Round Completed");
        rivalMoveMsg.setText("");
        rivalMoveMsg.open();
    }

    /** Initializes the game 
     * 
     */
    private void initGame(final Display display) {
        runnable = new Runnable() {
            public void run() {
            	// for GUI painting and game logic
                display.timerExec(DELAY, this);
                if (cardChosenRedraw) {
                    redraw();
                    cardChosenRedraw = false;                    
                }
                if(roundComplete){
                	roundComplete = false;
                	displayMB();
                	
                	Card chosenCard = cardSet[chosenCardNum - 1];
                	if(chosenCard.getDefense() > 0) card5.setCardInfo(chosenCard); // if stays alive
                	else { // card is thrown from the game, draw a new card
                		card5 = deck.getNextCard();
                		if(card5 == deck.getEmptyCard()){
                			cardSet[chosenCardNum - 1] = null;
                		}
                		else{
                			cardSet[chosenCardNum - 1] = new Card(card5.getAttack(), 
                				card5.getDefense(), card5.getName());
                			
                			turnList.add(cardSet[chosenCardNum - 1]);
                		}
                	}
                	
                	
                	// clear upper cards
                	undoChosenCard();
                	card6 = deck.getEmptyCard();
                	
                	cardChosenRedraw = true;
                	
                	initRound(playerWon);
                	finishedInit = true;
                }
            }
        };
        display.timerExec(DELAY, runnable);
    }

    /** Draws the GUI objects every round
     * 
     */    
    private class BoardPaintListener implements PaintListener {

        public void paintControl(PaintEvent e) { 
            Color col = new Color(shell.getDisplay(), 100, 0, 0);
            col.dispose();
            e.gc.setAntialias(SWT.ON);
            
            drawObjects(e);
            addButtonToolTips();
           
            e.gc.dispose();
        }
    }

   
    /** Draws a card on the GUI
     * 
     */
    private void drawCard(PaintEvent e, int x, int y, ICard card) {
        Image portrait = card.getImage();
        e.gc.drawImage(cardTemplate, x, y);
        e.gc.drawImage(portrait, x + 32, y + 32);
        if (card != deck.getEmptyCard()) {
            e.gc.setBackground(WHITE);
            e.gc.drawImage(attackIcon, x + 20, y + 50 + portrait.getBounds().height);
            e.gc.drawImage(shieldIcon, x + 25 + portrait.getBounds().width, y + 45 + portrait.getBounds().height);
            e.gc.drawString(card.getName(), x + portrait.getBounds().width - (3 * card.getName().length()), y + 14);
            e.gc.drawString(card.getDefense().toString(), x + 30 + portrait.getBounds().width, y + 40 + portrait.getBounds().height + 33);
            e.gc.drawString(card.getAttack().toString(), x + 25, y + 40 + portrait.getBounds().height + 33);
        }
    }

    /** Draws all the GUI cards objects
     * 
     */
    private void drawObjects(PaintEvent e) {
        drawCard(e, card1Xpos, playerCardsYPos, card1);
        drawCard(e, playercardsXOffset + card1Xpos, playerCardsYPos, card2);
        drawCard(e, (2*playercardsXOffset) + card1Xpos, playerCardsYPos, card3);
        drawCard(e, (3*playercardsXOffset) + card1Xpos, playerCardsYPos, card4);
        drawCard(e, card5Xpos, playingCardsYPos, card5);
        drawCard(e, card5Xpos+playingcardsXOffset, playingCardsYPos, card6);
    }

}
